// node symbol_demo01.js

let test01 = function() {
    let s1 = Symbol('foo')
    let s2 = Symbol('bar')

    console.log(s1)
    console.log(s2)
    console.log(s1.toString())
    console.log(s2.toString())
}

// test01()

let test02 = function() {
    const obj = {
        toString() {
            return 'abc'
        }
    }

    const sym = Symbol(obj)
    console.log(sym)
}

// test02()


let test03 = function() {
    let _

    let s1 = Symbol()
    let s2 = Symbol()

    _ = s1 === s2
    console.log(_)
    
    s1 = Symbol('foo')
    s2 = Symbol('foo')

    _ = s1 === s2
    console.log(_)
}

// test03()

let test04 = function() {
    let _
    let sym = Symbol('My symbol')

    _ = String(sym)
    console.log(_)
    _ = sym.toString()
    console.log(_)
}

// test04()

// Symbol.prototype.description

let test05 = function() {
    const sym = Symbol('foo')

    let _ = sym.description
    console.log(_)
}

// test05()

let test06 = function() {
    const COLOR_RED = Symbol()
    const COLOR_GREEN = Symbol()

    let getComplement = function(color) {
        switch (color) {
            case COLOR_RED:
                return COLOR_GREEN
            case COLOR_GREEN:
                return COLOR_RED
            default:
                throw new Error('Undefined color')
        }
    }
}


let test07 = function() {
    const obj = {}
    let a = Symbol('a')
    let b = Symbol('b')

    obj[a] = 'Hello'
    obj[b] = 'World'

    const objectSymbols = Object.getOwnPropertySymbols(obj)
    console.log(objectSymbols)
}

// test07()

let test08 = function() {
    const obj = {}
    const foo = Symbol('foo')

    obj[foo] = 'bar'

    for(let i in obj) {
        console.log(i)
    }

    let _

    _ = Object.getOwnPropertyNames(obj)
    console.log(_)

    _ = Object.getOwnPropertySymbols(obj)
    console.log(_)
}

// test08()

// Reflect.ownKeys

let test09 = function() {
    let obj = {
        [Symbol('my_key')]: 1,
        enum: 2,
        nonEnum: 3
    }

    let _ = Reflect.ownKeys(obj)
    console.log(_)
}

// test09()

// 由于以 Symbol 值作为键名，不会被常规方法遍历得到。我们可以利用这个特性，为对象定义一些非私有的、但又希望只用于内部的方法。

let test10 = function() {
    let size = Symbol('size')

    class Collection {
        constructor() {
            this[size] = 0
        }

        add(item) {
            this[this[size]] = item
            this[size]++
        }

        static sizeOf(instance) {
            return instance[size]
        }
    }

    let _
    let x = new Collection()
    _ = Collection.sizeOf(x)
    console.log(_)

    x.add('foo')
    _ = Collection.sizeOf(x)
    console.log(_)

    _ = Object.keys(x)
    console.log(_)
    _ = Object.getOwnPropertyNames(x)
    console.log(_)
    _ = Object.getOwnPropertySymbols(x)
    console.log(_)
}

// test10()

// Symbol.for

let test11 = function() {
    let _
    let s1 = Symbol.for('foo')
    let s2 = Symbol.for('foo')

    _ = s1 === s2
    console.log(_)
}

// test11()

let test12 = function() {
    let _
    let s1 = Symbol.for("foo")
    _ = Symbol.keyFor(s1)
    console.log(_)

    let s2 = Symbol("foo")
    _ = Symbol.keyFor(s2)
    console.log(_)
}

// test12()

// Symbol.hasInstance

let test13 = function() {
    let _
    class MyClass {
        [Symbol.hasInstance](foo) {
            return foo instanceof Array
        }
    }

    _ = [1, 2, 3] instanceof new MyClass()
    console.log(_)
}

// test13()

let test14 = function() {
    let _
    class Even {
        static [Symbol.hasInstance](obj) {
            return Number(obj) % 2 === 0
        }
    }

    // 等同于
    const Even0 = {
        [Symbol.hasInstance](obj) {
            return Number(obj) % 2 === 0
        }
    }

    _ = 1 instanceof Even
    console.log(_)
    _ = 1 instanceof Even0
    console.log(_)
    _ = 2 instanceof Even
    console.log(_)
    _ = 12345 instanceof Even
    console.log(_)
}

// test14()

let test15 = function() {
    let _
    let arr1 = ['c', 'd']
    _ = ['a', 'b'].concat(arr1, 'e')
    console.log(_)
    _ = arr1[Symbol.isConcatSpreadable]
    console.log(_)

    let arr2 = ['c', 'd']
    arr2[Symbol.isConcatSpreadable] = false
    _ = ['a', 'b'].concat(arr2, 'e')
    console.log(_)
}

// test15()

let test16 = function() {
    let _
    let obj = {length:2, 0: 'c', 1: 'd'}
    _ = ['a', 'b'].concat(obj, 'e')
    console.log(_)

    obj[Symbol.isConcatSpreadable] = true
    _ = ['a', 'b'].concat(obj, 'e')
    console.log(_)
}

// test16()

let test17 = function() {
    let _
    class A1 extends Array {
        constructor(args) {
            super(args)
            this[Symbol.isConcatSpreadable] = true
        }
    }
    class A2 extends Array {
        constructor(args) {
            super(args)
        }
        get [Symbol.isConcatSpreadable]() {
            return false
        }
    }
    let a1 = new A1()
    a1[0] = 3
    a1[1] = 4
    let a2 = new A2()
    a2[0] = 5
    a2[1] = 6

    _ = [1, 2].concat(a1).concat(a2)
    console.log(_)
}

// test17()

let test18 = function() {
    let _
    class MyArray extends Array {}

    const a = new MyArray(1, 2, 3)
    const b = a.map(x => x)
    const c = a.filter(x => x > 1)

    _ = b instanceof MyArray
    console.log(_)
    _ = c instanceof MyArray
    console.log(_)
}

// test18()

let test19 = function() {
    let _
    class MyArray extends Array {
        static get [Symbol.species]() {
            return Array
        }
    }

    const a = new MyArray()
    const b = a.map(x => x)

    _ = b instanceof MyArray
    console.log(_)
    _ = b instanceof Array
    console.log(_)
}

// test19()


let test20 = function() {
    let _

    // String.prototype.match(regexp)
    // 等同于
    // regexp[Symbol.match](this)

    class MyMatcher {
        [Symbol.match](str) {
            return 'hello world'.indexOf(str)
        }
    }

    _ = 'e'.match(new MyMatcher())
    console.log(_)
}

// test20()

let test21 = function() {
    // String.prototype.replace(searchValue, replaceValue)
    // 等同于
    // searchValue[Symbol.replace](this, replaceValue)

    const x = {}
    x[Symbol.replace] = (...s) => console.log(s)

    'Hello'.replace(x, 'World')
}

// test21()

let test22 = function() {
    let _
    // String.prototype.search(regexp)
    // 等同于
    // regexp[Symbol.search](this)

    class MySearch {
        constructor(value) {
            this.value = value
        }
        [Symbol.search](str) {
            return str.indexOf(this.value)
        }
    }

    _ = 'footbar'.search(new MySearch('foo'))
    console.log(_)
}

// test22()

let test23 = function() {
    let _
    // String.prototype.split(separator, limit)
    // 等同于
    // separator[Symbol.split](this, limit)

    class MySplitter {
        constructor(value) {
            this.value = value
        }
        [Symbol.split](str) {
            let index = str.indexOf(this.value)
            if (index === -1) {
                return str
            }
            return [
                str.substr(0, index),
                str.substr(index + this.value.length)
            ]
        }
    }

    _ = 'foobar'.split(new MySplitter('foo'))
    console.log(_)

    _ = 'foobar'.split(new MySplitter('bar'))
    console.log(_)

    _ = 'foobar'.split(new MySplitter('baz'))
    console.log(_)
}

// test23()

let test24 = function() {
    let _
    const myIterable = {}

    myIterable[Symbol.iterator] = function* () {
        yield 1
        yield 2
        yield 3
    }

    _ = [...myIterable]
    console.log(_)
}

// test24()

let test25 = function() {
    let _
    class Collection {
        *[Symbol.iterator]() {
            let i = 0
            while(this[i] !== undefined) {
                yield this[i]
                ++i
            }
        }
    }

    let myCollection = new Collection()
    myCollection[0] = 1
    myCollection[1] = 2

    for (let val of myCollection) {
        console.log(val)
    }
}

// test25()

let test26 = function() {
    let _
    let obj = {
        [Symbol.toPrimitive](hint) {
            switch (hint) {
                case 'number':
                    return 123
                case 'string':
                    return 'str'
                case 'default':
                    return 'default'
                default:
                    throw new Error()
            }
        }
    }

    _ = 2 * obj
    console.log(_)
    _ = 3 + obj
    console.log(_)
    _ = obj == 'default'
    console.log(_)
    _ = String(obj)
    console.log(_)
}

// test26()

// 也就是说，这个属性可以用来定制[object Object]或[object Array]中object后面的那个字符串。

let test27 = function() {
    let _

    _ = ({[Symbol.toStringTag]: 'Foo'}.toString())
    console.log(_)

    class Collection {
        get [Symbol.toStringTag]() {
            return 'xxx'
        }
    }
    let x = new Collection()
    _ = Object.prototype.toString.call(x)
    console.log(_)
}

// test27()

let test28 = function() {
    let _

    _ = Array.prototype[Symbol.unscopables]
    console.log(_)

    _ = Object.keys(Array.prototype[Symbol.unscopables])
    console.log(_)
}

// test28()



let test29 = function() {

    class MyClass0 {
        foo() { return 1; }
    };

    var foo = function() { return 2; };

    // 使用`npx babel-node symbol_demo01.js`运行, 下面的语句报错
    // 直接使用`node symbol_demo01.js`运行, 正常

    with (MyClass0.prototype) {
        var _ = foo();
        console.log(_);
    };

    class MyClass1 {
        foo() { return 1; }
        get [Symbol.unscopables]() {
            return { foo: true };
        }
    }

    // 下面的语句报错

    with(MyClass1.prototype) {
        var _ = foo()
        console.log(_)
    }
}

test29()


















